001 package EVolve.util.settings;
002
003 import java.io.*;
004 import java.util.*;
005
006 public class IniFile {
007 private Map sections;
008 private File file;
009 private boolean modified;
010
011 public IniFile(String fileName) throws IOException {
012 this(new File(fileName));
013 }
014
015 public IniFile(File file) throws IOException {
016 this.sections = new LinkedHashMap();
017 this.file = file;
018
019 if (file.exists()) {
020 load();
021 }
022 }
023
024 private static String parseValue(String value) {
025 if (value == null) {
026 return null;
027 }
028
029 value = value.trim();
030 if (value.length() > 0) {
031 char s[] = value.toCharArray();
032 if (s[0] == '"') {
033 // Find the next '"'
034 boolean escaped = false;
035 int i;
036 loop:
037 for (i = 1; i < s.length; i++) {
038 if (escaped) {
039 escaped = false;
040 } else {
041 switch (s[i]) {
042 case '\\':
043 escaped = true;
044 break;
045 case '"':
046 break loop;
047 default:
048 break;
049 }
050 }
051 }
052
053 if (s[i] == '"') {
054 if (i < s.length - 1) {
055 String commentStr = value.substring(i+1).trim();
056 if (!commentStr.startsWith(";")) {
057 throw new RuntimeException("Expression expected to be a comment");
058 }
059 }
060 value = value.substring(1, i);
061 }
062 }
063 }
064
065 return unescape(value);
066 }
067
068 public static String escape(String value) {
069 if (value == null) {
070 return null;
071 }
072
073 String result = "";
074 char[] s = value.toCharArray();
075 for (int i = 0; i < s.length; i++) {
076 char c = s[i];
077 switch (c) {
078 case '?':
079 result += "\\?";
080 break;
081 case '\b':
082 result += "\\b";
083 break;
084 case '\t':
085 result += "\\t";
086 break;
087 case '\n':
088 result += "\\n";
089 break;
090 case '\f':
091 result += "\\f";
092 break;
093 case '\r':
094 result += "\\r";
095 break;
096 case '"':
097 result += "\\\"";
098 break;
099 case '\'':
100 result += "\\'";
101 break;
102 case '\\':
103 result += "\\\\";
104 break;
105 default:
106 result += c;
107 break;
108 }
109 }
110
111 return result;
112 }
113
114 public static String unescape(String value) {
115 if (value == null) {
116 return null;
117 }
118
119 String result = "";
120 char[] s = value.toCharArray();
121 for (int i = 0; i < s.length; i++) {
122 char c = s[i];
123 if (c == '\\') {
124 if (i == (s.length - 1)) {
125 return result + '\\';
126 } else {
127 c = s[++i];
128 switch (c) {
129 case '?':
130 result += '?';
131 break;
132 case 'b':
133 result += '\b';
134 break;
135 case 't':
136 result += '\t';
137 break;
138 case 'n':
139 result += '\n';
140 break;
141 case 'f':
142 result += '\f';
143 break;
144 case 'r':
145 result += '\r';
146 case 'x':
147 {
148 i++;
149 String hexString = "";
150 while (i < s.length) {
151 c = s[i];
152 if (Character.digit(c, 16) >= 0) {
153 hexString += c;
154 i++;
155 } else {
156 break;
157 }
158 }
159
160 if (hexString.length() <= 0) {
161 i--;
162 result += "\\x";
163 } else {
164 result += (char) Short.parseShort(hexString, 16);
165 }
166 }
167 break;
168 case '0':
169 case '1':
170 case '2':
171 case '3':
172 case '4':
173 case '5':
174 case '6':
175 case '7':
176 case '8':
177 case '9':
178 {
179 String octalString = "";
180 while (i < s.length) {
181 c = s[i];
182 if (Character.digit(c, 8) >= 0) {
183 octalString += c;
184 i++;
185 } else {
186 break;
187 }
188 }
189
190 if (octalString.length() <= 0) {
191 result += "\\";
192 } else {
193 result += (char) Short.parseShort(octalString, 8);
194 }
195 }
196 break;
197 default:
198 result += '\\' + c;
199 break;
200 }
201 }
202 } else {
203 result += c;
204 }
205 }
206
207 return result;
208 }
209
210 public void load() throws IOException {
211 BufferedReader in = new BufferedReader(new FileReader(file));
212 String line;
213 String currentSection = null;
214
215 while ((line = in.readLine()) != null) {
216 line = line.trim();
217 if (line.length() <= 0) {
218 continue;
219 }
220
221 char c = line.charAt(0);
222
223 switch (c) {
224 case ';':
225 continue;
226 case '[':
227 currentSection = line.substring(1, line.length() - 1);
228 put(currentSection);
229 break;
230 default:
231 {
232 int eqPos = line.indexOf('=');
233 if (eqPos < 0) {
234 throw new RuntimeException("Invalid INI file format -- Expected key=value");
235 }
236
237 String key = line.substring(0, eqPos).trim();
238 String value = parseValue(line.substring(eqPos + 1));
239
240 put(currentSection, key, value);
241 }
242 break;
243 }
244 }
245
246 modified = false;
247
248 in.close();
249 }
250
251 public void write() throws IOException {
252
253 PrintStream out = new PrintStream(new FileOutputStream(file));
254
255 Iterator sectionIt = sections.keySet().iterator();
256 while (sectionIt.hasNext()) {
257 Object sectionName = (String) sectionIt.next();
258 Map keysToValues = (Map) sections.get(sectionName);
259
260 out.println("[" + sectionName + "]");
261 Iterator keysIt = keysToValues.keySet().iterator();
262 while (keysIt.hasNext()) {
263 String key = (String) keysIt.next();
264 String value = (String) keysToValues.get(key);
265 out.println(key + "=\"" + escape(value) + "\"");
266 }
267 out.println();
268 }
269 }
270
271 public boolean contains(String section) {
272 if (section == null) {
273 return false;
274 }
275
276 return sections.containsKey(section);
277 }
278
279 public boolean contains(String section, String key) {
280 if (section == null || key == null) {
281 return false;
282 }
283
284 if (sections.containsKey(section)) {
285 Map keysToValues = (Map) sections.get(section);
286 return keysToValues.containsKey(key);
287 }
288
289 return false;
290 }
291
292 public boolean put(String section) {
293 if (section == null) {
294 return false;
295 }
296
297 if (!sections.containsKey(section)) {
298 sections.put(section, new LinkedHashMap());
299 }
300
301 modified = true;
302 return true;
303 }
304
305 public boolean put(String section, String key, String value) {
306 if (section == null || key == null) {
307 return false;
308 }
309
310 if (value == null) {
311 value = "";
312 }
313
314 Map keysToValues;
315 if (sections.containsKey(section)) {
316 keysToValues = (Map) sections.get(section);
317 } else {
318 keysToValues = new LinkedHashMap();
319 sections.put(section, keysToValues);
320 }
321
322 keysToValues.put(key, value);
323
324 modified = true;
325
326 return true;
327 }
328
329 public String get(String section, String key) {
330 if (sections.containsKey(section)) {
331 Map keysToValues = (Map) sections.get(section);
332 if (keysToValues.containsKey(key)) {
333 return (String) keysToValues.get(key);
334 }
335 }
336
337 return null;
338 }
339
340 public Set getSections() {
341 return new HashSet(sections.keySet());
342 }
343
344 public Set getKeys(String section) {
345 if (section == null) {
346 return null;
347 }
348
349 if (sections.containsKey(section)) {
350 Map keysToValues = (Map) sections.get(section);
351 return new HashSet(keysToValues.keySet());
352 }
353
354 return null;
355 }
356
357 public Set getValues(String section) {
358 if (section == null) {
359 return null;
360 }
361
362 if (sections.containsKey(section)) {
363 Map keysToValues = (Map) sections.get(section);
364 return new HashSet(keysToValues.values());
365 }
366
367 return null;
368 }
369
370 public void close() throws IOException {
371 if (sections != null) {
372 if (modified) {
373 this.write();
374 }
375 sections = null;
376 }
377 }
378
379 public void finalize() {
380 try {
381 this.close();
382 } catch (IOException e) {
383 // not much we can do now...
384 }
385 }
386 }
387